package org.yxt.message.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * netty 01
 *
 * @author yxt
 * <p>
 * Sep 18, 2017
 */
public class NettyServer {

    private static Logger logger = LoggerFactory.getLogger(NettyServer.class);

    // 存储访问session 用户id Integer 用户ChannelHandlerContext
    public static ConcurrentHashMap<Integer, ChannelHandlerContext> socketChanelKeyMap = new ConcurrentHashMap<Integer, ChannelHandlerContext>();

    /**
     * 粘包处理分割符 \r\n
     */
    public final String DELIMITER_CHART = "\r\n";
    // iP
    public final String HOST = "127.0.0.1";
    // 端口
    public static int PORT = 8181;
    // 构建服务端 线程组 定义为异步IO
    // 服务器线程组
    private static EventLoopGroup bossGroup = null;
    // 读写线程组
    private static EventLoopGroup workGroup = null;
    private ServerBootstrap serverBootstrap = null;
    private static ChannelFuture future = null;
    private Channel channel = null;

    // 单例模式获取连接
    private NettyServer() {
    }

    // 使用volatile让所线程能正确访问
    private static volatile NettyServer instance;

    // 实现单例
    public static NettyServer getInstance() {
        if (null == instance) {
            // 同步防止未启动完成北调用而出错
            synchronized (NettyServer.class) {
                if (null == instance) {
                    instance = new NettyServer();
                }
            }
        }
        return instance;
    }

    // 注入配置
    public void run() {
        try {
            if (null != bossGroup) {
                bossGroup.shutdownGracefully();
            }
            if (null != workGroup) {
                workGroup.shutdownGracefully();
            }
            bossGroup = new NioEventLoopGroup(1);
            // 业务线程 默认线程条数默认
            workGroup = new NioEventLoopGroup();
            // 启动引擎
            serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workGroup);
            // 设置通道 异步
            serverBootstrap.channel(NioServerSocketChannel.class);
            // 设置TCP参数
            // 处理不了设置排队数量-服务
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 128);
            // 不足设置长度也发数据 在客户端我们需要这样设置：bootstap.option(ChannelOption.TCP_NODELAY, true)  在服务器端是在worker的Channel端设置属性boot.childOption(ChannelOption.TCP_NODELAY, true);
            serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
            // 设置为保持链接-数据读写
            serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
            // 设置管道
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    // 心跳 10 秒钟
                    // 该类的第一个参数是指定读操作空闲秒数，
                    // 第二个参数是指定写操作的空闲秒数，
                    // 第三个参数是指定读写空闲秒数，当有操作操作超出指定空闲秒数时，
                    // 便会触发UserEventTriggered事件。所以我们只需要在自己的handler中截获该事件，
                    // 然后发起相应的操作即可（比如说发起心跳操作）服务端先不加入心跳 客户主动连接
                    // pipeline.addLast(new IdleStateHandler(15, 0, 0, TimeUnit.SECONDS));
                    // DelimiterBasedFrameDecoder用来解决以特殊符号作为消息结束符的粘包问题
                    // 消息结束符的粘包处理 使用\r\n做结束符 自定义结束符
                    ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER_CHART.getBytes());
                    pipeline.addLast("framer", new DelimiterBasedFrameDecoder(2048, delimiter));
                    // 字符串解码 和 编码
                    pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
                    pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
                    pipeline.addLast(new NettyServerSimpleHandler());
                }

            });
            // 创建连接
            doConnect();
        } catch (Exception e) {
            logger.error("配置注入异常 {}", e.getMessage());
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }

    // 创建服务
    protected void doConnect() {
        if (null != channel && channel.isActive()) {
            return;
        }
        try {
            // 获取连接数据 不能使用同步 因为有返回listener
            // 绑定端口 同步返回
            future = serverBootstrap.bind(HOST, PORT).sync();
            logger.info("-------netty server start ip:{} port:{}-------------", HOST, PORT);
            // 同步等待关闭
            future.channel().closeFuture().sync();
            future.addListener(new ChannelFutureListener() {
                public void operationComplete(ChannelFuture futureListener) throws Exception {
                    if (futureListener.isSuccess()) {
                        channel = futureListener.channel();
                        logger.info("netty server start successfully");
                    } else {
                        logger.error("Failed to netty server start, try connect after 10s");
                        futureListener.channel().eventLoop().schedule(new Runnable() {

                            public void run() {
                                doConnect();
                            }

                        }, 10, TimeUnit.SECONDS);
                    }
                }
            });
        } catch (Exception e) {
            logger.error("netty server start expection {}", e.getMessage());
            e.printStackTrace();
        }

    }

    /**
     * 关闭连接
     */
    public void closeNettyConn() {
        try {
            if (null != future) {
                future.channel().closeFuture().sync();
            }
            if (null != bossGroup) {
                bossGroup.shutdownGracefully();
                workGroup.shutdownGracefully();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // dos 测试socket连接 telnet localhost 8080
    public static void main(String[] args) {
        // NettyServer.getInstance().closeNettyConn();
        // NettyServer.getInstance().run();
    }
}
